import Vue from 'vue'
import request from '@/request/index'
import { cloneDeep } from 'lodash'

/*

模型 -> 为ui组件的渲染提供数据支持 -> ui组件的需要的数据不同 模型应做出不同的调整 

操作类 -> 模型控制数据请求与处理的手段注重多种请求方式需求 取消请求 重新请求 数据回滚 虽然模型都具有对应的方式 但是全部交由操作类型实现

Axios类 -> 请求注重如何请求数据与处理数据

为什么需要响应式数据?
需要通过响应式处理 让ui组件自动更新 或是处理不同的情况

可以耦合在一起 但是分开可以保持 每一部分的职责单一更加专注 并且可以更好的复用

table组件不同的请求需求 --> 交给Operation --> 控制AxiosProxy --> 得到结果

*/

// 支持需要数组类型的ui组件 纯数据
export class Store {
  constructor(data = []) {
    // 提供tableui组件的数据模型
    this.view = Vue.observable({
      // 这里可以放置一些默认数据状态
      data,
      page: 1,
      pageSize: 10,
      total: 0,
      // 不同的初始状态 0 代表初始状态 1 代表请求中 2 代表请求成功 3 代表请求失败
      status: 0
    })

    // 提供分页器组件的数据模型
    this.filterPage = {
      page: 1,
      pageSize: 10
    }

    // 记录上一次成功的操作
    this.lastSuccessOperation
    // 记录上一次操作 无论成功与失败
    this.lastOperation

    // 根据不同的请求可以分出不同的请求方式 1. 直接请求 2. 缓存请求 3. 前端分页请求 -> 2/3都可以看作是直接请求的变种
    // 根据实际的需求 选择不同的请求方式 并将请求结果交给store 方便操作类进行调用
    /*
      这里为什么需要一个微任务? 
      因为请求的类型是由Store的子类确定的 但是类属性的初始化顺序是 父类属性 -> 子类属性 所以会出访问子类属性的错误 
      所以需要一个微任务来确保子类属性初始化完成
    */
    Promise.resolve().then(() => {
      // 传入this获取store的proxy.type信息
      this.proxy = createProxyInstance(this)
      // 应对微任务处理异步请求的问题
      if (this._isNeedLoad) {
        const operation = new Operation(this)
        // 调用请求操作
        operation.execute(this.filter, this.filterPage)
      }
    })
  }

  // 数据请求能力
  // 请求数据 -> 提供tableui组件第一次渲染数据
  // 请求时时机分为两种 1. 组件创建时就申请数据(出现this.proxy.type的问题) 2. 组件在需要时才会请求数据(不会出现问题)
  async loadData(params) {
    // 提供搜索能力的过滤条件或是其他情况
    this.filter = params || {}
    // 通过判断请求类的load方法是否存在的方式来判断是否可以使用操作类进行请求
    if (this.proxy.load) {
      // 实际请求调用操作类来实现 为操作类提供请求参数和Store对象
      const operation = new Operation(this)
      // 调用请求操作
      await operation.execute(this.filter, this.filterPage)
    } else {
      this._isNeedLoad = true
    }
  }
  // 分页请求 -> 提供分页ui组件的分页功能
  // loadData的语法糖
  loadPage({ page, pageSize }) {
    this.filterPage.page = page || 1
    this.filterPage.pageSize = pageSize || 10
    // 调用方法
    this.loadData()
  }
  // 取消请求 -> 提供取消重复请求的能力
  abort() {
    this.lastOperation.abort()
  }
  // 重新请求 -> 提供请求失败后重新请求的能力
  refresh() {}
  // 重置请求 -> ?
  reset() {}
  // 更多的请求方法...
}

/*
操作类

理清 这次操作 上一次成功的操作 最后一次操作 上一次的最后操作 这四种关系
*/
class Operation {
  // 接受store对象 使用store对象中的数据 为操作Axios类请求提供支持
  constructor(store) {
    // 使用store中的数据
    this.store = store
    // 使用store中的请求方式
    this.proxy = store.proxy
    // 记录操作类的状态
    this.status = 0
  }

  // 需要一个执行器去执器去执行 请求类中的方法
  async execute(filter, filterPage) {
    // 只有操作状态为0时 才会执行函数
    if (this.status !== 0) return
    // 这么做好 忘了问deepseek
    this.filter = filter || {}
    this.filterPage = filterPage || {}

    // 上一次请求是上一次的最后的操作
    this.pre = this.store.lastOperation

    // 更新操作链关系
    if (this.store.lastOperation) {
      this.store.lastOperation.last = this
    }

    // 无论操作的结果如何都保存一下这次操作的状态 让store保存一下
    this.store.lastOperation = this

    // 每次请求都需要将store的状态修改为1 因为只有这个一个状态是没有被维护的 23都在axios类在请求时维护了
    this.store.view.status = 1
    /*
      竟态问题 GET请求直接取消上一次请求(或者忽略) POST请求等待上一次请求的完成(或者忽略)
    */

    // 执行请求类的操作方法传入this 提供store operation中的数据
    await this.proxy.load(this)

    // 如果是取消请求
    if (this.status === 4) {
      // 操作取消 状态回滚
      if (!this.last) {
        this.store.view.status = 2
        this.store.lastOperation.reset()
      }
      return
    }

    // 请求结束后修改一下store的状态
    this.store.view.status = this.status

    // 如果请求成功保存保存这次成功的操作用于之后可能的回滚操作
    if (this.status === 2) {
      // 此时的this里面保存着成功结果的信息 比如 data page pageSize total 让store保存一下
      this.store.lastSuccessOperation = this

      // 操作类的结果与store的结果合并 深拷贝 避免浅拷贝导致数据共享在修改时出现未知错误
      this.reset()
    }
  }

  // 重新执行
  refresh() {
    const operation = new Operation(this.store)
    operation.execute(this.filter, this.filterPage)
  }
  // 取消请求
  abort() {
    this.cancel.abort()
  }
  // 合并store.view 和 operation.view
  reset() {
    Object.assign(this.store.view, cloneDeep(this.view))
  }
}

// 请求类
// 直接请求
class AxiosProxy {
  constructor(config) {
    // 一定需要一个url
    this.url = config.url || ''
  }

  // 请求方法
  async load(operation) {
    // 从操作类中获取 获取axios请求需要的参数
    const { filter, filterPage } = operation

    // 更该操作类的状态也是更该模型的通讯状态
    operation.status = 1

    // 取消请求
    operation.pre?.abort()

    // 创建请求取消操作
    const controller = new AbortController()
    const signal = controller.signal

    // 赋值给操作类 用于取消请求
    operation.cancel = controller

    try {
      // 发送实际的请求
      const res = await request.get(this.url, {
        params: {
          // 数据序列化
          ...this.setLoadPage(filterPage),
          ...filter
        },
        signal
      })

      // 处理成功的请求
      if (this.getSuccess(res)) {
        operation.status = 2
        // 将请求结果保存到操作类 便于赋值给store或者进行其他操作
        operation.view = {
          data: this.getData(res),
          page: this.getPage(res),
          pageSize: this.getPageSize(res),
          total: this.getTotal(res)
        }
      }
    } catch (error) {
      if (error.code === 'ERR_CANCELED') {
        // 取消操作
        operation.status = 4
      } else {
        // 请求失败
        operation.status = 3
      }
    }
  }
  // 请求结果逻辑复用/请求结果反序列化
  getSuccess(res) {
    return res?.status === 200
  }
  getData(res) {
    // 上面没解构 这里多data 可以解构也可以不解
    return res?.data?.data || []
  }
  getPage(res) {
    return res?.data?.page || 1
  }
  getPageSize(res) {
    return res?.data?.pageSize || 10
  }
  getTotal(res) {
    return res?.data?.total || 0
  }
  // 请参数序列化 -> 转换为后端需要的参数格式 此处只是模拟 以实际为准
  setLoadPage(filterPage) {
    return {
      P: filterPage.page,
      PS: filterPage.pageSize
    }
  }
  // 取消请求
  abort() {
    this.cancel.abort()
  }
}
// 缓存请求
class CacheProxy extends AxiosProxy {}
// 前端分页请求
class PaginationProxy extends AxiosProxy {}

// 工程函数(utils) 用于处理选择使用的请求方式
function createProxyInstance(store) {
  // 根据实际的需求 选择不同的请求方式
  const type = store.proxy.type
  if (type === 'axiosProxy') {
    return new AxiosProxy(store.proxy, store)
  } else if (type === 'cacheProxy') {
    return new CacheProxy(store.proxy, store)
  } else if (type === 'axiosAllProxy') {
    return new PaginationProxy(store.proxy, store)
  }
}
